1. Field of the Invention
This invention relates generally to computer programming, and more particularly to a method of managing nested monitor locks.
2. Description of the Related Art
High-level programming languages offer features that enable complex tasks to be solved. Java™, for example, offers platform independence, an object orientated programming model, and the ability to distribute processing tasks to multiple threads of operation.
By way of background, Java™ is an interpreted language that utilizes a Java™ Virtual Machine (JVM) as an interpreter. The JVM is a software layer that converts Java™ intermediate language (bytecode) into machine language. A common procedure performed to load an application is to call a class loading library. The class loading library defines the various classes that will be needed to load the application.
Constructing applications that distribute processing tasks among multiple threads of operation requires control over the order in which threads execute. Without this control, critical code or data might be processed by multiple threads simultaneously. The Java™ language provides monitors for mutual exclusion control over critical code sections which can protect data access. As defined herein, monitors provide mutual exclusion, through both the ability to acquire and release ownership of protected code and data structures, and through the use of wait and notify operations. A wait( ) by definition releases a monitor and returns either after a timeout or a notify( ), guaranteed to own the monitor. Thus, a “monitor” will define a mutual exclusion lock with a condition variable.
For this monitor definition, a single thread can acquire a monitor multiple times. This is known as the recursion count. A release of a monitor, only releases a single acquisition, so the same number of releases are required as acquisitions in order to free the monitor for other threads. The wait( ) operation completely frees the monitor, making the monitor available for other threads.
To prevent deadlocks from the use of multiple monitors, the users of these monitors follow lock ordering rules for acquiring these related locks. Specifically, a thread which wants to acquire MI, must first ensure that it has acquired MO. The thread releases the locks in the inverse order, releasing MI, then releasing MO. This generally prevents the classic deadlock situation. We use the abbreviation MO for outer monitor and MI for inner monitor to remind us of the lock ordering rules.
FIG. 1 illustrates a high level diagram 100, which includes a Classloading library 104 being interfaced with JVM 106, which may be part of a computer system 102. In operation, the Classloading library 104 includes APIs, which will enable calls to the JVM 106. In the example, the Classloading library 104 has an example loadClass( ) method that acquires an outer monitor N times. The JVM 106 includes methods, such as JVM internally defined “defineclass( )” method that acquires inner monitors.
The monitors associated with the loadClass( ) method of the Classloading library 104 are called outer monitors (MO), while those associated with the internal defineclass( ) method of the JVM 106 are called inner monitors (MI). Because the monitors exist in inner and outer pairs, they are called nested monitors. An MO is designed to lock critical code or a critical data structure of a higher level method, such as the Classloading library 104, while the MI is designed to lock critical code or a critical data structure of a lower level method, such as the defineclass( ) method of the JVM 106. Traditionally, in the JVM 106, the class loading library API loadClass( ) is implemented as a synchronized procedure, synchronized on the class loader object lock. That is, class loading is performed in a serial manner, traversing through class tree structure hierarchies.
From an application design viewpoint, it is desirable at times to create custom class loaders. Programmers can use these to provide custom class loading methods. Some programmers use these to avoid tree structure hierarchies of class loaders when a problem warrants such a strategy. For some of these cases, this will cause a deadlock situation, and some programmers explicitly break the synchronization in their implementations which then causes class loading on a class loader to be done in parallel instead of serialized. A thread of the application breaks the synchronization by implementing a “wait” on the class loader object lock. Then, another thread of the application processes class loading within the same JVM for that same class loader.
When the JVM needs to load classes for an application, it uses its own internal lock to protect internal class loading objects. In Java™, the defineclass( ) method requires loading all the superclasses of the application's classes. In some JVMs, this process is done recursively, requiring calls out to the application loadClass( ) method for each superclass. For this procedure to work correctly, the initial application thread needs to complete the defineclass( ) processing and other threads need to wait on the initial thread's completion.
Unfortunately, this can cause the JVM to deadlock and consequentially the application will fail to run properly. That is, a situation can occur where one thread is trying to acquire MO, in order to acquire MI and complete an action. The second thread owns the outer monitor (MO) and is waiting on MI for the first thread to complete an action. Due to the lock ordering rules noted above, the first thread must wait for the second thread to release MO in order to proceed, thus the deadlock.
In view of the foregoing, there is a need for a method that allows nested monitor locks to be safely released when custom class loaders break serialized class loading. The method should allow thread safe operation of custom and standard class loaders without having to rewrite the existing class loading library model or rewriting the semantics of Java™.